package com.mutual.assistance.media.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import com.mutual.assistance.exception.BizException;
import com.mutual.assistance.exception.code.ExceptionCode;
import com.mutual.assistance.media.converter.MediaConverterParams;
import com.mutual.assistance.media.mapper.MediaFilesMapper;
import com.mutual.assistance.media.mapper.MediaProcessMapper;
import com.mutual.assistance.media.model.dto.MediaFilesDTO;
import com.mutual.assistance.media.model.dto.UploadFileResultDto;
import com.mutual.assistance.media.model.po.MediaFilesPO;
import com.mutual.assistance.media.model.po.MediaProcessPO;
import com.mutual.assistance.media.model.vo.DownloadFileParamsVO;
import com.mutual.assistance.media.model.vo.QueryMediaParamsVO;
import com.mutual.assistance.media.model.vo.UploadFileParamsVO;
import com.mutual.assistance.media.service.MediaFileService;
import com.mutual.assistance.media.utils.SecurityUtil;
import com.mutual.assistance.response.PageParams;
import com.mutual.assistance.response.PageResult;
import io.minio.GetObjectArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.*;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;

import static com.mutual.assistance.exception.code.ExceptionCode.INTERNAL_SERVER_ERROR;

/**
 * @author chenyuxin
 * @version 1.0
 * @description 煤资服务
 * @date 2023/4/23 16:31
 */
@Service
@Slf4j
public class MediaFileServiceImpl implements MediaFileService {

    @Resource
    private MinioClient minioClient;

    @Resource
    private MediaFileService proxy;

    @Resource
    private MediaProcessMapper mediaProcessMapper;

    @Resource
    private MediaFilesMapper mediaFilesMapper;

    @Resource
    private MediaConverterParams mediaConverterParams;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 图片存储的桶
     */
    @Value("${minio.bucket.files}")
    private String bucketFiles;

    @Override
    @GlobalTransactional
    public UploadFileResultDto uploadFile(
            UploadFileParamsVO uploadFileParamsDto, byte[] bytes,
            String folder, String objectName
    ) {
        log.info("获取到传入数据：{}", uploadFileParamsDto);
        log.info("folder：{}，objectName：{}", folder, objectName);
        if (StringUtils.isEmpty(folder)) {
            // 未指定图片存储的路径按照时间进行存储
            folder = getFileFolder(new Date(), true, true, true);
        } else if (!folder.contains("/")) {
            // 指定文件路径
            folder = folder + "/";
        }

        log.info("folder：{}", folder);

        // 获取文件的md5值
        String fileMd5 = DigestUtils.md5Hex(bytes);
        log.info("文件md5：{}", fileMd5);

        // 获取文件名称
        String filename = uploadFileParamsDto.getFilename();
        if (StringUtils.isEmpty(objectName)) {
            // 没有指定文件名称，用md5摘要和文件的后缀组成新的名称
            objectName = fileMd5 + filename.substring(filename.lastIndexOf("."));
        }

        objectName = folder + objectName;
        log.info("获取文件文件夹与文件名称：{}", objectName);

        // 添加到MinIO
        addMediaFilesToMinIO(bytes, bucketFiles, objectName);

        // 添加到数据表
        MediaFilesPO mediaFilesPO = proxy.addMediaFilesToDb(uploadFileParamsDto, objectName, fileMd5, bucketFiles);

        // 返回结果
        // 准备返回数据
        UploadFileResultDto uploadFileResultDto = mediaConverterParams.mediaFilesPO2UploadFileResultDto(mediaFilesPO);
        log.info("返回数据：{}", uploadFileResultDto);

        return uploadFileResultDto;
    }

    /**
     * @param bytes      文件字节数组
     * @param bucket     桶
     * @param objectName 对象名称
     * @description 将文件写入minIO
     */
    @Override
    public void addMediaFilesToMinIO(byte[] bytes, String bucket, String objectName) {

        log.info("开始上传到minio");

        String extension = null;
        if (objectName.contains(".")) {
            // 取objectName当中的拓展名
            extension = objectName.substring(objectName.lastIndexOf("."));
        }

        // 获取拓展名对应的媒体类型
        String contentType = getContentTypeByExtend(extension);
        log.info("文件拓展名：{}", contentType);

        try {
            // 上传到minio
            ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucket)
                            .object(objectName)
                            // 第一个参数为流，第二个参数为文件大小，stream.available()可以读取文件大小。
                            // 第三个参数为分片大小（-1表示5m，最大不要超过5T，最多10000个分片）
                            .stream(stream, stream.available(), -1)
                            .contentType(contentType)
                            .build()
            );
        } catch (Exception e) {
            e.printStackTrace();
            log.error("上传文件到文件系统出错：{}", e.getMessage());
            throw BizException.wrap(ExceptionCode.FAILED_TO_UPLOAD_IMAGE.getCode(), ExceptionCode.FAILED_TO_UPLOAD_IMAGE.getMsg());
        }
    }

    /**
     * 将文件信息保存到数据库
     *
     * @param uploadFileParamsVO 文件信息
     * @param objectName         文件名称
     * @param fileMd5            文件的摘要
     * @param bucket             桶名称
     * @return 对应的Media表信息
     */
    @Transactional
    @Override
    public MediaFilesPO addMediaFilesToDb(
            UploadFileParamsVO uploadFileParamsVO, String objectName, String fileMd5, String bucket
    ) {
        log.info("上传到数据库");
        // 保存到数据库
        MediaFilesPO mediaFiles = mediaFilesMapper.selectById(fileMd5);
        log.info("获取数据库数据：{}", mediaFiles);

        if (mediaFiles == null) {
            // 插入文件表
            mediaFiles = mediaConverterParams.uploadFileParamsVO2(uploadFileParamsVO);
            mediaFiles.setFileId(fileMd5);
            mediaFiles.setId(fileMd5);
            mediaFiles.setBucket(bucket);
            mediaFiles.setFilePath(objectName);
            // 图片、mp4的视频可以直接设置url
            String extension = null;
            String filename = uploadFileParamsVO.getFilename();
            if (StringUtils.isNotEmpty(filename) && filename.contains(".")) {
                extension = filename.substring(filename.lastIndexOf("."));
            }
            String contentType = getContentTypeByExtend(extension);
            if (contentType.contains("image")) {
                mediaFiles.setUrl("/" + bucketFiles + "/" + objectName);
                mediaFiles.setTags("图片");
            }

            if (contentType.contains("mp4")) {
                mediaFiles.setUrl("/" + bucketFiles + "/" + objectName);
                mediaFiles.setTags("视频");
            }
            mediaFiles.setCreateTime(LocalDateTime.now());
            // 默认为正常状态
            mediaFiles.setStatus("1");
            // 这里默认让他审核通过
            mediaFiles.setAuditMind("002003");
            SecurityUtil.UserBO userBO = SecurityUtil.getUser();
            if (userBO != null) {
                mediaFiles.setUserId(userBO.getId().toString());
            }
            // 对avi视频添加到待处理任务表
            if (contentType.equals("video/x-msvideo")) {
                // 待处理的任务表
                MediaProcessPO mediaProcess = new MediaProcessPO();
                BeanUtils.copyProperties(mediaFiles, mediaProcess);
                // 1表示未处理
                mediaProcess.setStatus("1");
                mediaProcessMapper.insert(mediaProcess);
            }

            int insert = mediaFilesMapper.insert(mediaFiles);

            if (insert < 0) {
                log.error("插入失败");
                throw BizException.wrap(ExceptionCode.SQL_EX.getCode(),
                        ExceptionCode.SQL_EX.getMsg() + "mediaFiles: {}", mediaFiles);
            }

        }
        return mediaFiles;
    }


    //根据日期拼接目录
    private String getFileFolder(Date date, boolean year, boolean month, boolean day) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        //获取当前日期字符串
        String dateString = sdf.format(new Date());
        //取出年、月、日
        String[] dateStringArray = dateString.split("-");
        StringBuilder folderString = new StringBuilder();
        if (year) {
            folderString.append(dateStringArray[0]);
            folderString.append("/");
        }
        if (month) {
            folderString.append(dateStringArray[1]);
            folderString.append("/");
        }
        if (day) {
            folderString.append(dateStringArray[2]);
            folderString.append("/");
        }
        return folderString.toString();
    }

    // 根据拓展名来拿对应匹配的煤资类型
    private String getContentTypeByExtend(String extension) {
        // 资源的媒体类型,默认的未知的二进制流类型
        String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;

        if (StringUtils.isNotEmpty(extension)) {
            ContentInfo contentInfo = ContentInfoUtil.findExtensionMatch(extension);
            if (contentInfo != null) {
                contentType = contentInfo.getMimeType();
            }
        }

        return contentType;
    }

    public static final String key = "com:mutual:assistance:media:app:apk";
    public static final String GET_ERROR = "查询文件出错了";

    @Override
    public File downloadFileFromMinIO(DownloadFileParamsVO downloadFileParamsVO) {
        File file = downloadFileParamsVO.getFile();
        File fileCache = (File) redisTemplate.opsForValue().get(key);
        if (fileCache != null) {
            return file;
        }

        try (
                InputStream inputStream = minioClient.getObject(
                        GetObjectArgs.builder()
                                .bucket(downloadFileParamsVO.getBucket())
                                .object(downloadFileParamsVO.getObjectName())
                                .build()
                );
                FileOutputStream outputStream = new FileOutputStream(file);
        ) {
            IOUtils.copy(inputStream, outputStream);
            redisTemplate.opsForValue().set(key, file);
            return file;
        } catch (Exception e) {
            log.error("查询分块文件出错");
            throw new BizException(INTERNAL_SERVER_ERROR.getCode(), GET_ERROR);
        }
    }

    @Override
    public PageResult<MediaFilesDTO> queryMediaFiles(PageParams pageParams, QueryMediaParamsVO queryMediaParamsVO) {
        log.info("获取前端参数：{}", queryMediaParamsVO);
        log.info("h获取到分页参数：{}", pageParams);
        //构建查询条件对象
        LambdaQueryWrapper<MediaFilesPO> queryWrapper = new LambdaQueryWrapper<>();

        String fileType = queryMediaParamsVO.getFileType();
        queryWrapper.eq(fileType != null, MediaFilesPO::getFileType, fileType);

        String filename = queryMediaParamsVO.getFilename();
        queryWrapper.eq(filename != null, MediaFilesPO::getFilename, filename);

        String auditStatus = queryMediaParamsVO.getAuditStatus();
        queryWrapper.eq(auditStatus != null, MediaFilesPO::getAuditStatus, auditStatus);

        String status = queryMediaParamsVO.getStatus();
        queryWrapper.eq(status != null, MediaFilesPO::getStatus, status);

        String userId = queryMediaParamsVO.getUserId();
        queryWrapper.eq(userId != null, MediaFilesPO::getUserId, userId);

        String id = queryMediaParamsVO.getId();
        queryWrapper.eq(id != null, MediaFilesPO::getId, id);

        //分页对象
        Page<MediaFilesPO> page = new Page<>(pageParams.getPageNo(), pageParams.getPageSize());
        // 查询数据内容获得结果
        Page<MediaFilesPO> pageResult = mediaFilesMapper.selectPage(page, queryWrapper);
        // 获取数据列表
        List<MediaFilesPO> list = pageResult.getRecords();
        log.info("获取到集合列表：{}", list);
        List<MediaFilesDTO> mediaFilesDTOs = mediaConverterParams.mediaFilesPO2MediaFilesDTOList(list);
        log.info("获取到转换集合列表：{}", mediaFilesDTOs);
        // 获取数据总数
        long total = pageResult.getTotal();
        // 构建结果集
        return new PageResult<>(mediaFilesDTOs, total, pageParams.getPageNo(), pageParams.getPageSize());
    }
}
